// Amf3Output.cpp: implementation of the Amf3Output class.
//
//////////////////////////////////////////////////////////////////////
//#include "stdafx.h"
#include "amf3_output.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
#define APPEND_SIZE		2048
//#define AMF_FUN_TIME(FUN_NAME) FunTimeHook _____GmFunTimeHook(#FUN_NAME);
#define AMF_FUN_TIME(FUN_NAME)

namespace Amf{


	Amf3Output::Amf3Output(int32 iSize)
	{
		m_Data.reserve(iSize);
	}

	Amf3Output::~Amf3Output()
	{
		m_Data.clear();
	}

	void Amf3Output::reset()
	{
		m_Data.clear();
		stringTable.clear();
		objectTable.clear();
	}
	
	//	protected:


	void Amf3Output::writeAMFInt(const int32& i)
	{
		AMF_FUN_TIME (Amf3Output::writeAMFInt);

		int32 iValue = i;
		if (iValue >= INT28_MIN_VALUE && iValue <= INT28_MAX_VALUE)
		{				
			// We have to be careful when the MSB is set, as (value >> 3) will sign extend.
			// We know there are only 29-bits of precision, so truncate. This requires
			// similar care when reading an integer.
			//i = ((i >> 3) & UINT29_MASK);
			iValue = iValue & UINT29_MASK; // Mask is 2^29 - 1
			writeAMFType(AMF3_INTEGER);
			writeUInt29(iValue);
		}
		else
		{
			// Promote large int to a double
			writeAMFDouble((float64)iValue);
		}
	}
	
	void Amf3Output::writeUInt29(int32 ref)
	{
		AMF_FUN_TIME (Amf3Output::writeUInt29);

		// Represent smaller integers with fewer bytes using the most
		// significant bit of each byte. The worst case uses 32-bits
		// to represent a 29-bit number, which is what we would have
		// done with no compression.
		
		// 0x00000000 - 0x0000007F : 0xxxxxxx
		// 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
		// 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
		// 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
		// 0x40000000 - 0xFFFFFFFF : throw range exception
		if (ref < 0x80)
		{
			// 0x00000000 - 0x0000007F : 0xxxxxxx
			writeByte(ref);
		}
		else if (ref < 0x4000)
		{
			// 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
			writeByte(((ref >> 7) & 0x7F) | 0x80);
			writeByte(ref & 0x7F);
			
		}
		else if (ref < 0x200000)
		{
			// 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
			writeByte(((ref >> 14) & 0x7F) | 0x80);
			writeByte(((ref >> 7) & 0x7F) | 0x80);
			writeByte(ref & 0x7F);
			
		}
		else if (ref < 0x40000000)
		{
			// 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
			writeByte(((ref >> 22) & 0x7F) | 0x80);
			writeByte(((ref >> 15) & 0x7F) | 0x80);
			writeByte(((ref >> 8) & 0x7F) | 0x80);
			writeByte((byte)ref & 0xFF);
			
		}
		else
		{
			// 0x40000000 - 0xFFFFFFFF : throw range exception
			// throw new MessageException("Integer out of range: " + ref);
		}
	}

	void Amf3Output::writeAMFUTF(const wchar_t* s)
	{
		AMF_FUN_TIME (Amf3Output::writeAMFUTF)
		string str = WToU8(s);
		writeUInt29((str.length()<< 1) | 1);
		m_Data.insert(m_Data.end(), str.begin(), str.end());
		return;
	}

	void Amf3Output::writeAMFString(const wchar_t* s)
	{
		AMF_FUN_TIME (Amf3Output::writeAMFString)
		writeByte(AMF3_STRING);
		writeStringWithoutType(s);
	}
	
	void Amf3Output::writeStringWithoutType(const wchar_t* s)
	{
		AMF_FUN_TIME (Amf3Output::writeStringWithoutType)
		if (wcslen(s) == 0)
		{
			// don't create a reference for the empty string,
			// as it's represented by the one byte value 1
			// len = 0, ((len << 1) | 1).
			writeUInt29(1);
			return;
		}
		
		if (!byReference(s))
		{
			writeAMFUTF(s);
			return;
		}
	}
	
	void Amf3Output::writeAMFArray(AmfArrayPtr pArr)
	{
		AMF_FUN_TIME (Amf3Output::writeAMFArray)
		if (pArr == NULL)	// || pArr->IsNull())
		{
			writeAMFNull();
			return;
		}

		writeByte(AMF3_ARRAY);
		
		if(!byReference(pArr))
		{
			AmfObjectArray& arr = pArr->GetValue();
			int32 ilength = arr.size();
			writeUInt29((ilength << 1) | 1);
			
			// Send an empty string to imply no named keys
			writeStringWithoutType(EMPTY_STRING);
			
			for(int32 i = 0; i < ilength; i ++)
			{
				writeObject(arr[i]);
			}
		}
	}

	void Amf3Output::writeArrayInt32( AmfExArrayIntPtr pArr )
	{
		AMF_FUN_TIME (Amf3Output::writeArrayInt32)
		if (pArr == NULL)	// || pArr->IsNull())
		{
			writeAMFNull();
			return;
		}

		writeByte(AMF3_ARRAY);

		if(!byReference(pArr))
		{
			StdIntArray& arr = pArr->GetValue();
			int32 ilength = arr.size();
			writeUInt29((ilength << 1) | 1);

			// Send an empty string to imply no named keys
			writeStringWithoutType(EMPTY_STRING);

			for(int32 i = 0; i < ilength; i ++)
				writeAMFInt(arr[i]);
		}
	}

	void Amf3Output::writeArrayFloat32( AmfExArrayFloat64Ptr pArr )
	{
		AMF_FUN_TIME (Amf3Output::writeArrayFloat32)
		if (pArr == NULL)	// || pArr->IsNull())
		{
			writeAMFNull();
			return;
		}

		writeByte(AMF3_ARRAY);

		if(!byReference(pArr))
		{
			StdDoubleArray& arr = pArr->GetValue();
			int32 ilength = arr.size();
			writeUInt29((ilength << 1) | 1);

			// Send an empty string to imply no named keys
			writeStringWithoutType(EMPTY_STRING);

			for(int32 i = 0; i < ilength; i ++)
				writeAMFDouble(arr[i]);
		}
	}

	void Amf3Output::writeAMFByteArray(AmfByteArrayPtr pByteArr)
	{
		AMF_FUN_TIME (Amf3Output::writeAMFByteArray)
		if (pByteArr == NULL)	// || pByteArr->IsNull())
		{
			writeAMFNull();
			return;
		}

		writeByte(AMF3_BYTEARRAY);
		
		if (!byReference(pByteArr))
		{
			StdByteArray &bytearr = pByteArr->GetValue();
			
			int32 length = bytearr.size();
			
			// Write out an invalid reference, storing the length in the unused 28-bits.
			writeUInt29((length << 1) | 1);
			
			//memcpy(&m_Data[0] + m_iIndex, &bytearr[0], length);
			m_Data.insert(m_Data.end(), bytearr.begin(), bytearr.end());
		}
	}
	
	bool Amf3Output::byReference(const wstring& s)
	{
		AMF_FUN_TIME (Amf3Output::byReference::string)
		map<wstring, int32>::iterator it = stringTable.find(s);
		if(it != stringTable.end())
		{
			int32 refNum = (*it).second;
			writeUInt29(refNum << 1);
			
			return true;
		}
		else
		{
			int32 refNum = stringTable.size();
			stringTable[s] = refNum;
			
			return false;
		}
	}

	bool Amf3Output::byReference(AmfObjectPtr obj)
	{
		AMF_FUN_TIME (Amf3Output::byReference::obj)
		int32 pvalue = (int32)obj.GetObject();
		map<int32, int32>::iterator it = objectTable.find(pvalue);
		if(it != objectTable.end())
		{
			int32 refNum = (*it).second;
			writeUInt29(refNum << 1);
			
			return true;
		}
		else
		{
			int32 refNum = objectTable.size();
			objectTable[pvalue] = refNum;
			
			return false;
		}
	}
		
	const unsigned char* Amf3Output::GetData(int32 &iLen)
	{
		iLen = m_Data.size();
		return iLen > 0 ? m_Data.pbegin() : NULL;
	}

	void Amf3Output::writeObject(AmfObjectPtr obj)
    {
		AMF_FUN_TIME (Amf3Output::writeObject)
        if (obj == NULL)	// || obj->IsNull())
        {
            writeAMFNull();
            return;
        }
		
		int32 iType = ((AmfObjectPtr)obj)->DataType();
		switch(iType) {
		case AMF3_STRING:
			writeAMFString(((AmfStringPtr)obj)->GetValue());
			break;
		case AMF3_INTEGER:
			writeAMFInt(((AmfIntPtr)obj)->GetValue());
			break;
		case AMF3_NUMBER:
			writeAMFDouble(((AmfDoublePtr)obj)->GetValue());
			break;
		case AMF3_TRUE:
		case AMF3_FALSE:
			writeAMFBoolean(((AmfBoolPtr)obj)->GetValue());
			break;
		case AMF3_DATE:
			writeDate((AmfDatePtr)obj);
			break;
		case AMF3_ARRAY:
			writeAMFArray((AmfArrayPtr)obj);
			break;
		case AMF3_OBJECT:
			writeCustomObject((AmfAsObjectPtr)obj);
			break;
		case AMF3_BYTEARRAY:
			writeAMFByteArray((AmfByteArrayPtr)obj);
			break;
		case AMF3_ARRAY_EX_FLOAT64:
			writeArrayFloat32((AmfExArrayFloat64Ptr)obj);
			break;
		case AMF3_ARRAY_EX_INT32:
			writeArrayFloat32((AmfExArrayIntPtr)obj);
			break;
		case AMF3_XML:
		case AMF3_XMLSTRING:
		case AMF3_UNDEFINED:
		default:
            writeAMFNull();
			return;
		}
    }

	void Amf3Output::writeCustomObject(AmfAsObjectPtr obj)
    {
		AMF_FUN_TIME (Amf3Output::writeCustomObject)
		if(obj == NULL)
		{
			writeAMFNull();
			return;
		}

        writeByte(AMF3_OBJECT);
		
		if (!byReference(obj))
		{
			bool externalizable = false;
			bool dynamic = true;
			int32 count = 0;
			
			writeUInt29(3 | (externalizable ? 4 : 0) | (dynamic ? 8 : 0) | (count << 4));
			writeStringWithoutType(obj->GetName().c_str());

			MapStrPtr& TmpMap = obj->GetValue();
			MapStrPtr::iterator it = TmpMap.begin();

			while (it != TmpMap.end())
			{
				writeStringWithoutType((*it).first.c_str());
				writeObject((*it).second);

				it ++;
			}
			
			writeStringWithoutType(EMPTY_STRING);
		}
    }

} //namespace Amf
